package com.k8.common.aop.internal.interceptor;

import com.k8.common.aop.internal.advice.annotation.Order;
import com.k8.common.aop.internal.invocation.MethodInvocation;
import com.k8.common.aop.internal.invocation.ReflectiveMethodInvocation;
import com.k8.common.aop.internal.joinpoint.JoinPoint;
import com.k8.common.util.AopUtil;
import lombok.Getter;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

/**
 * 对所有拦截器提供统一的功能支持，例如参数的位置确定，类型缓存等，并提供默认的排序序号
 * */
@Getter
public abstract class ProxyMethodExecutor implements MethodInterceptor{

    //切面所在对象和切面方法
    private Object target;
    private Method targetMethod;
    private Parameter[] adviceMethodParas;
    private Object[] cacheParas;



    int tagIndex=-1;//标记返回值所在的参数位置或异常的参数位置
    int joinPointIndex=-1;

    public ProxyMethodExecutor(Object target, Method targetMethod) {
        Parameter[] parameters = targetMethod.getParameters();
        initArgs(parameters,targetMethod);
        this.adviceMethodParas=parameters;
        this.target = target;
        this.targetMethod = targetMethod;
    }
    /*
    * 切面拦截器的统一父类
    * 为该方法的参数先确定参数的位置，只能传递JoinPoint类型和如 after，throwing等可以传递返回参数的
    * */
    private void initArgs(Parameter[] parameters,Method targetMethod) {
        if (parameters.length>2) throw new IllegalArgumentException("Length of advice method greater than 2.");
        else if (parameters.length == 1){
            Parameter parameter = parameters[0];
            if (judgeJointPointArgument(parameter)){
                joinPointIndex=0;
            }else if (judgeArgument(parameter,targetMethod)){
                tagIndex=0;
            }else {
                throw new IllegalArgumentException("Advice method parameter error.");
            }
        }else if (parameters.length==2){
            if (!judgeArgs(parameters,0,targetMethod)){
                if (!judgeArgs(parameters,1,targetMethod)){
                    throw new IllegalArgumentException("Advice method parameter error.");
                }
            }
        }
    }

    private boolean judgeArgs(Parameter[] parameters,int index,Method targetMethod){
        if (judgeJointPointArgument(parameters[index])){
            joinPointIndex=index;
            if (judgeArgument(parameters[index^1],targetMethod)){
                tagIndex=index^1;
                return true;
            }else {
                throw new IllegalArgumentException("Advice method parameter error.");
            }
        }
        return false;
    }

    boolean judgeJointPointArgument(Parameter parameter){
        return parameter.getType().isAssignableFrom(JoinPoint.class);
    }
    abstract boolean judgeArgument(Parameter parameter,Method targetMethod);


   /* public void init(){
        this.adviceMethodParas= targetMethod.getParameters();
        if (adviceMethodParas.length>2){
            throw new IllegalArgumentException("count of argument > 2");
        }
        String paraName=null;
        if (targetMethod.isAnnotationPresent(AfterReturning.class)){
            String result = targetMethod.getAnnotation(AfterReturning.class).result();
            if (!result.isEmpty()){
                paraName=result;
            }
        }else if (targetMethod.isAnnotationPresent(Throwing.class)){
            String throwing = targetMethod.getAnnotation(Throwing.class).throwing();
            if (!throwing.isEmpty()){
                paraName=throwing;
            }
        }
        int index=0;
        for (Parameter para : adviceMethodParas) {
            if (para.getType().isAssignableFrom(JoinPoint.class)){
                if (joinPointIndex>=0) throw new RuntimeException("only one joinPoint Para");
                joinPointIndex=index;
            }else if (paraName!=null&&para.isNamePresent()&&paraName.equals(para.getName())){
                tagIndex=index;
                return;
            }else if (joinPointIndex<0){
                throw new IllegalArgumentException("joinPoint is null");
            }else if (targetMethod.isAnnotationPresent(AfterReturning.class)){
                throw new IllegalArgumentException("result argument is null");
            }else {
                throw new IllegalArgumentException("throwing argument is null");
            }
            index++;
        }
    }*/
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        preInvokeProceed();
        return invoke0(invocation);
    }
    /*
    * 提前切面参数，避免嵌套调用数据异常
    * */
    public  void preInvokeProceed(){
        Object[] arguments=new Object[adviceMethodParas.length];
        if (joinPointIndex>=0){
            MethodInvocation methodInvocation = ExposeInvocationInterceptor.currentMethodInvocation();
            if (methodInvocation instanceof ReflectiveMethodInvocation pmi){
                arguments[joinPointIndex]=pmi.getJoinPoint();
            }
        }
        cacheParas=arguments;
    }

    abstract Object invoke0(MethodInvocation invocation) throws Throwable;


    protected void invokeAdvice(Object result) throws InvocationTargetException, IllegalAccessException {
        AopUtil.makeAccessible(targetMethod);
        if (adviceMethodParas.length==0){
            targetMethod.invoke(target);
        }else {
            if (result!=null&&tagIndex>=0){
                cacheParas[tagIndex]=result;
            }
            targetMethod.invoke(target,cacheParas);
        }
    }
    protected void invokeAdvice(Throwable throwable) throws InvocationTargetException, IllegalAccessException {
        AopUtil.makeAccessible(targetMethod);
        if (adviceMethodParas.length==0){
            targetMethod.invoke(target);
        }else {
            if (throwable!=null&&tagIndex>=0){
                cacheParas[tagIndex]=throwable;
            }
            targetMethod.invoke(target,cacheParas);
        }
    }
    protected void invokeAdvice() throws InvocationTargetException, IllegalAccessException {
        AopUtil.makeAccessible(targetMethod);
        if (adviceMethodParas.length==0) targetMethod.invoke(target);
        else {
            targetMethod.invoke(target,cacheParas);
        }
    }
    protected Object invokeAround() throws InvocationTargetException, IllegalAccessException {
        AopUtil.makeAccessible(targetMethod);
        if (adviceMethodParas.length==0) return targetMethod.invoke(target);
        else {
            return targetMethod.invoke(target,cacheParas);
        }
    }
    public int getOrder(){
        if (targetMethod.isAnnotationPresent(Order.class)){
            return targetMethod.getAnnotation(Order.class).order();
        }
        Order annotation = target.getClass().getAnnotation(Order.class);
        if (annotation!=null){
            return annotation.order();
        }
        return 69999999;
    }
    public void setCacheParas(Object[] cacheParas) {
        this.cacheParas = cacheParas;
    }

}
